/*
//  Controller.m
//  Agate Rule Tester
//
//  Created by Jim Turner on 11/20/05.

License: GNU General Public License
This program is free software; you can redistribute it and/or modify it under 
the terms of the GNU General Public License as published by the Free Software 
Foundation; either version 2 of the License, or (at your option) any later 
version.

This program is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 
details.

You should have received a copy of the GNU General Public License along 
with this program; if not, write to the Free Software Foundation, Inc., 
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
http://www.gnu.org/licenses/licenses.html#GPL

*/

#import "Controller.h"
#import "JTRule.h"

@implementation Controller

#pragma mark Controller Startup
- (id) init
{
	if( self = [super init] )
	{
		ruleList     = [[NSMutableArray new] retain];
		baseRuleList = nil;
		
		// It's important to note that both the controller and JTFileData have their
		// own perl interpreter objects.  They are not sharing one as the controller
		// needs the ability to test regex's as they are input which can happen before
		// a JTFileData object is instantiated.
		perl = [[JTPerlInterface alloc] init];

		// Init our path-saving strings
		savedTranslationPath    = [NSMutableString new];
		savedTranslatedTextPath = [NSMutableString new];
		savedLastOpenPath       = [NSMutableString new];
	}

	return( self );
}

- (void) awakeFromNib
{
	// Set the font to something that should be a bit easier on the eyes
	[findStringTextView setFont:[NSFont fontWithName:@"Courier" size:12]];
	[replaceStringTextView setFont:[NSFont fontWithName:@"Courier" size:12]];
	[translationTextView setFont:[NSFont fontWithName:@"Courier" size:12]];

	// Tell NSApplication that we are its delegate
	[[NSApplication sharedApplication] setDelegate: self];

	// Tell the tableView that we're its target and set what it should call when 
	// an uneditable cell is double-clicked.
	[ruleTable setTarget:self];
	[ruleTable setDoubleAction: @selector( tableDoubleClicked: )];

	// Deactivate the New->Translation stuff... it's not working 
	// These don't work if the parent menu has "Auto Enables Items" checked
	// in the NIB.
	[menuNewTranslation setEnabled:NO];
	[menuNewTranslationWithCurrentBase setEnabled:NO];

	// Tell the rule dropdown what to populate with and where to initially set itself at
	[placeRuleWhereDropdown removeAllItems];
	[placeRuleWhereDropdown addItemsWithTitles:[NSArray arrayWithObjects:@"Before Current Line",
																		 @"After Current Line",
																		 @"Top of Table",
																		 @"Bottom of Table",
																		 nil]];
																		 
	[placeRuleWhereDropdown selectItemAtIndex:1];

	// Clear the ascii error textfield
	[asciiError setStringValue:@""];
	[asciiError setToolTip:@""];
}

#pragma mark Error Handling
- (IBAction)cancelError:(id)sender
{
	[errorWindow orderOut:sender];
	
	[NSApp endSheet:errorWindow returnCode:1];
}

- (IBAction)okError:(id)sender
{
	[errorWindow orderOut:sender];
	
	[NSApp endSheet:errorWindow returnCode:0];
}

#pragma mark File Handling
- (IBAction)openFile:(id)sender
{
	NSOpenPanel *openPanel = [NSOpenPanel openPanel];
	NSString    *openPath;

	// Make some things easier depending on where things are stored
	if( [[sender title] isNotEqualTo:@"Load Base Translation"] )
	{
		if( [savedLastOpenPath length] > 0 )
		{
			openPath = [savedLastOpenPath stringByDeletingLastPathComponent];
		}
		else
		{
			openPath = [[NSString stringWithString:@"~"] stringByExpandingTildeInPath];
		}
	}
	else
	{
		openPath = [[NSString stringWithString:@"~"] stringByExpandingTildeInPath];
	}

	[openPanel setAllowsMultipleSelection:NO];
	[openPanel setCanChooseDirectories:NO];

	[openPanel beginSheetForDirectory:openPath
				file:@""
				types:nil 
				modalForWindow:controlWindow 
				modalDelegate:self 
				didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:)
				contextInfo:[sender title]];
}

- (IBAction)saveFile:(id)sender
{
	NSSavePanel *savePanel = [NSSavePanel savePanel];
	NSString    *fileType;
	NSString    *savePath;
		
	[savePanel setTreatsFilePackagesAsDirectories:NO];
	[savePanel setCanCreateDirectories:YES];

	// Make some things a bit easier depending on how we're saving
	if( [[sender title] isEqualTo:@"Save Translated Text"] )
	{
		if( [savedTranslatedTextPath length] > 0 )
		{
			fileType = [savedTranslatedTextPath lastPathComponent];
			savePath = [savedTranslatedTextPath stringByDeletingLastPathComponent];
		}
		else
		{
			fileType = [NSString stringWithString:@"indesign_test.txt"];
			savePath = [[NSString stringWithString:@"~"] stringByExpandingTildeInPath];
		}
	}
	else if( [[sender title] isEqualTo:@"Save Translation"] )
	{
		if( [savedTranslationPath length] > 0 )
		{
			fileType = [savedTranslationPath lastPathComponent];
			savePath = [savedTranslationPath stringByDeletingLastPathComponent];
		}
		else
		{
			fileType = @"";
			savePath = [[NSString stringWithString:@"~"] stringByExpandingTildeInPath];
		}
	}
	else
	{
		fileType = @"";
		savePath = [[NSString stringWithString:@"~"] stringByExpandingTildeInPath];
	}

	
	[savePanel beginSheetForDirectory:savePath
					file:fileType 
					modalForWindow:controlWindow 
					modalDelegate:self
					didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:)
					contextInfo:[sender title]];
}

- (IBAction)newTranslation:(id)sender
{
	if( [[sender title] isEqualTo:@"Translation"] )
	{
		/* They want a brand-new translation.  Wipe out the base translation. */
		[baseRuleList release];
		[baseTranslationStatus setStringValue:[NSString stringWithString:@"Base loaded: none"]];
	}
	
	/* Wipe the current translation */
	NSLog( @"-- fileData retain count: %d", [fileData retainCount] );
NS_DURING
	[fileData release];
//	NSLog( @"-- fileData %@", fileData );
NS_HANDLER
	NSLog( @"failed to get last retainCount... exception thrown" );
NS_ENDHANDLER

	fileData = nil;
	[self updateTextView:@""];
	
	/* Finally, kill the ruleList and update the ruleTable */
//	[ruleList removeAllObjects];
//	[ruleTable reloadData];
}

#pragma mark Rule Manipulation
- (IBAction)newRule:(id)sender
{
	NSRange selectedRange;
	
	[findStringTextView setString:@""];
	[replaceStringTextView setString:@""];
	[regexTestError setStringValue:@""];

	[check_caseSensitiveSearch setState:NSOffState];
	[check_exhaustiveReplace setState:NSOffState];
	[check_replaceFirstOnly setState:NSOffState];
	[check_replaceLastOnly setState:NSOffState];
	[check_usePerlExpressions setState:NSOffState];
		
	[translationInformation setString:@""];

	[ruleWindow makeFirstResponder:findStringTextView];
	[placeRuleWhereDropdown setEnabled:YES];
	[placeRuleWhereDropdown selectItemAtIndex:1];

	/* 12.08.05 - jturner - Allows highlighted text in the translation window to appear as
	                        the find string when a new rule is invoked.
	*/
	selectedRange = [[[translationTextView selectedRanges] objectAtIndex:0] rangeValue];
	if( selectedRange.length != 0 )
	{
		[findStringTextView setString:[[translationTextView attributedSubstringFromRange:selectedRange] string]];
		
		/* Now select all of the text we just put in there so that if they don't want to use it, 
		   they just type over it.
		*/
		[findStringTextView setSelectedRange:NSMakeRange(0,[[findStringTextView string] length])];
	}

	[NSApp beginSheet:ruleWindow
			modalForWindow:controlWindow
			modalDelegate:self
			didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
			contextInfo:@"new rule"];
}

- (IBAction)deleteRule:(id)sender
{
	/* Make sure a rule is selected in the table first */
	if( [ruleTable selectedRow] < 0 )
	{
		[errorText setStringValue:@"To delete a rule, you must first select a rule from the list."];
	}
	else
	{
		[errorText setStringValue:@"Are you sure you want to delete this rule?"];
	}

	[NSApp beginSheet:errorWindow
			modalForWindow:controlWindow
			modalDelegate:self
			didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
			contextInfo:@"delete rule"];
	
}

- (IBAction)duplicateRule:(id)sender
{
	if( [ruleTable selectedRow] < 0 )
	{
		[errorText setStringValue:@"To duplicate a rule, you must first select a rule from the list."];

		[NSApp beginSheet:errorWindow
				modalForWindow:controlWindow
				modalDelegate:self
				didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
				contextInfo:@"duplicate rule - nothing selected"];
	}
	else
	{
		NSMutableDictionary *tmpDict;
		JTRule              *tmpRule;
		
		/* Get a copy of the currently selected rule */
		tmpDict = [[ruleList objectAtIndex:[ruleTable selectedRow]] mutableCopy];

		// Create a copy of the rule object
		tmpRule = [[tmpDict objectForKey:@"theRule"] copyWithZone:nil];

		// Update the copy of our rule with our new rule object
		[tmpDict removeObjectForKey:@"theRule"];
		[tmpDict setObject:tmpRule forKey:@"theRule"];
		
		// Add this new dictionary right back in to the rule list, right at where we're highlight
		// This rule will go above our current rule, but we'll force-select the lower one.
		[ruleList insertObject:[NSMutableDictionary dictionaryWithDictionary:tmpDict] atIndex:[ruleTable selectedRow]];
		
		/* Update the rule orders */
		[self updateRuleOrders:-1];
		
		/* reload the table data */
		[ruleTable reloadData];
		
		/* Set the highlighted line to our new line then scroll to it*/
		[ruleTable selectRowIndexes:[NSIndexSet indexSetWithIndex:([ruleTable selectedRow] + 1)] byExtendingSelection:NO];
		[ruleTable scrollRowToVisible:[ruleTable selectedRow]];

		// Release our temp objects.  These were copies, remember...
		[tmpRule release];
		[tmpDict release];
	}
}

- (IBAction)editRule:(id)sender
{
	[regexTestError setStringValue:@""];
	[placeRuleWhereDropdown setEnabled:NO];
	[placeRuleWhereDropdown selectItemAtIndex:1];

	if( [ruleTable selectedRow] < 0 )
	{
		[errorText setStringValue:@"To edit a rule, you must first select a rule from the list."];
		
		[NSApp beginSheet:errorWindow
				modalForWindow:controlWindow
				modalDelegate:self
				didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
				contextInfo:@"edit rule - nothing selected"];
	}
	else
	{
		JTRule *theRule;
		
		theRule = [[ruleList objectAtIndex:[ruleTable selectedRow]] objectForKey:@"theRule"];
	
		[findStringTextView setString:[theRule find]];
		[replaceStringTextView setString:[theRule replace]];

		[check_caseSensitiveSearch setState:[theRule isCaseSensitive]];
		[check_exhaustiveReplace setState:[theRule isExhaustiveReplace]];
		[check_replaceFirstOnly setState:[theRule isReplaceFirstOnly]];
		[check_replaceLastOnly setState:[theRule isReplaceLastOnly]];
		[check_usePerlExpressions setState:[theRule isRegex]];
		
		[translationInformation setString:[theRule translationInformation]];

		// Hide the "where to put the rule" drop down
	
		[NSApp beginSheet:ruleWindow
				modalForWindow:controlWindow
				modalDelegate:self
				didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
				contextInfo:@"edit rule"];
	}
}

- (IBAction)saveRule:(id)sender
{
	/* If this is a perl regex, test it to see if it at least compiles ok */
	if( ([check_usePerlExpressions state] == NSOnState) || 
		(NSEqualRanges( [[findStringTextView string] rangeOfString:@"[#perl]" options:NSAnchoredSearch], NSMakeRange( 0, 7 ) )) )
	{
		NSString *findRegexError, *replaceRegexError;
		
		findRegexError    = [perl testRegex:[findStringTextView string]];
	
		if( ![findRegexError isEqualTo:@""] )
		{
			// Find regex didn't pass
			NSLog( @"find: unable to save regex |%@|: %@", [findStringTextView string], findRegexError );
			[regexTestError setStringValue:[NSString stringWithFormat:@"Find: %@", findRegexError]];
			return;
		}

		// Make sure the replaceString actually has something
		if( ![[replaceStringTextView string] isEqualTo:@""] )
		{
			replaceRegexError = [perl testRegex:[replaceStringTextView string]];

			if( ![replaceRegexError isEqualTo:@""] )
			{
					// Replace regex didn't pass
				NSLog( @"replace: unable to save regex |%@|: %@", [replaceStringTextView string], replaceRegexError );
				[regexTestError setStringValue:[NSString stringWithFormat:@"Replace: %@", replaceRegexError]];
				return;
			}
		}
	}
	
	// A little sanity check
//	NSLog( @"%d %d", [[findStringTextView string] isEqualTo:@""], [[replaceStringTextView string] isEqualTo:@""] );
	if( ([[findStringTextView string] isEqualTo:@""]) &&
		([[replaceStringTextView string] isEqualTo:@""]) )
	{
		NSLog( @"both find and replace are empty..." );
		[regexTestError setStringValue:@"Nothing to find or replace... an empty rule?  I think not..."];
		return;
	}
	
	[ruleWindow orderOut:sender];
	
	[NSApp endSheet:ruleWindow returnCode:0];
}

- (IBAction)cancelSaveRule:(id)sender
{
	[ruleWindow orderOut:sender];
	
	[NSApp endSheet:ruleWindow returnCode:1];
}

- (IBAction)moveRuleDown:(id)sender
{
	if( [ruleTable selectedRow] >= 0 )
	{
		/* Is this rule at the end of the list?  if so, we can't move it down any futher */
		if( [ruleList count] == ([ruleTable selectedRow] + 1) )
		{
			NSLog( @"at the end... buh bye" );
			return;
		}
	
		NSMutableDictionary *tmpDictLower, *tmpDictUpper;
		
		tmpDictLower = [NSMutableDictionary dictionaryWithDictionary:[ruleList objectAtIndex:[ruleTable selectedRow]]];
		tmpDictUpper = [NSMutableDictionary dictionaryWithDictionary:[ruleList objectAtIndex:([ruleTable selectedRow] + 1)]];

//		NSLog( @"lower[%@]  upper[%@]", tmpDictLower, tmpDictUpper );
		
		[ruleList replaceObjectAtIndex:[ruleTable selectedRow] withObject:tmpDictUpper];
		[ruleList replaceObjectAtIndex:([ruleTable selectedRow] + 1) withObject:tmpDictLower];
		
		[self updateRuleOrders:-1];
		
		[ruleTable reloadData];
		
		/* Purty... moves the highlighted row down with the rule.  Scroll
		   to it if it isn't visible
		*/
		[ruleTable selectRowIndexes:[NSIndexSet indexSetWithIndex:([ruleTable selectedRow] + 1)] byExtendingSelection:NO];
		[ruleTable scrollRowToVisible:[ruleTable selectedRow]];
	}
}

- (IBAction)moveRuleUp:(id)sender
{
	if( [ruleTable selectedRow] >= 0 )
	{
		/* Is this rule at the start of the list?  if so, we can't move it up any futher */
		if( [ruleTable selectedRow] == 0 )
		{
			NSLog( @"at the beginning... buh bye" );
			return;
		}
	
		NSMutableDictionary *tmpDictLower, *tmpDictUpper;
		
		tmpDictLower = [NSMutableDictionary dictionaryWithDictionary:[ruleList objectAtIndex:([ruleTable selectedRow] - 1)]];
		tmpDictUpper = [NSMutableDictionary dictionaryWithDictionary:[ruleList objectAtIndex:[ruleTable selectedRow]]];

//		NSLog( @"lower[%@]  upper[%@]", tmpDictLower, tmpDictUpper );
		
		[ruleList replaceObjectAtIndex:([ruleTable selectedRow] - 1) withObject:tmpDictUpper];
		[ruleList replaceObjectAtIndex:[ruleTable selectedRow] withObject:tmpDictLower];
		
		[self updateRuleOrders:-1];
		
		[ruleTable reloadData];
		
		/* Purty... moves the highlighted row up with the rule.  Scroll
		   to it if it isn't visible
		*/
		[ruleTable selectRowIndexes:[NSIndexSet indexSetWithIndex:([ruleTable selectedRow] - 1)] byExtendingSelection:NO];
		[ruleTable scrollRowToVisible:[ruleTable selectedRow]];

	}
}

- (IBAction)ruleActiveStatusClicked:(id)sender
{
	JTRule *tmpRule;
	
	// Grabs the current rule Object for this row
	tmpRule = [[ruleList objectAtIndex:[sender selectedRow]] objectForKey:@"theRule"];
	
	// Updates the state of the checkbox
	if( [tmpRule activeState] == 1 )
	{
		[tmpRule setActive:0];
	}
	else
	{
		[tmpRule setActive:1];
	}

	// Updates the UI
	[ruleTable reloadData];
	
	// Re-process rules! (real-time updating, how cool is that?!)
	[self runRules:nil];
}

- (IBAction)ruleToggleAllRules:(id)sender
{
	JTRule *tmpRule;
	int x;
	
	for( x=0; x<[ruleList count]; x++ )
	{
		// Grabs the current rule Object for this row
		tmpRule = [[ruleList objectAtIndex:x] objectForKey:@"theRule"];
	
		/* Updates the state of the checkbox based on what the user is
		   trying to do */
		if( [[sender title] isEqualTo:@"Turn Off All Rules"] )
		{
			[tmpRule setActive:0];
		}
		else
		{
			[tmpRule setActive:1];
		}
	}

	// Updates the UI
	[ruleTable reloadData];
	
	// Re-process rules!
	[self runRules:nil];
}


- (IBAction)runRules:(id)sender
{
	/* Has anything been loaded into translationTextView yet? */
	if( [[translationTextView string] isEqualTo:@""] )
	{
		// Turn "show mnemonics" off if they happened to turn it on to get here
		[showDTMnemonicsCheckbox setState:FALSE];
		[fileData toggleDTMnemonics:FALSE];
	
		[errorText setStringValue:@"Load an agate file first before running rules."];	
		
		[NSApp beginSheet:errorWindow
				modalForWindow:controlWindow
				modalDelegate:self
				didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
				contextInfo:@"ran rules with no agate file loaded"];
	}
/*
	else if( ([ruleList count] == 0) && ([baseRuleList count] == 0) )
	{
		// There's no rules loaded yet... nothing to run
		[errorText setStringValue:@"There are no translation rules loaded.  Load either a saved or base translation first."];
		
		[NSApp beginSheet:errorWindow
				modalForWindow:controlWindow
				modalDelegate:self
				didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
				contextInfo:@"ran rules with no rules loaded"];
	}	
*/
	else
	{
		[self updateTextView:[fileData runRules:ruleList afterBaseRules:baseRuleList]];
	}
}	

/* This thing was a really really great idea.  Storing order in each rule object
   would have sucked a lot. */
- (void) updateRuleOrders:(int)savePosition
{
	NSEnumerator         *ruleListEnum;
	id obj;
	int counter = 0;

	// if savePosition is not -1, Just pull off the last guy first, figure out where the table is 
	// highlighted, then reinsert the last object where you need to based off the selection.  Then re-number.
	// Still no global counters!! Yay!
	if( savePosition != -1 )
	{
		int insertIntoPosition = [ruleTable selectedRow];
		id lastObj             = [ruleList lastObject];
		[ruleList removeLastObject];
		
		switch( savePosition )
		{
			case 0:
				// Before current line
				// Do nothing because we'll insert into the array at the same
				// position, which moves everyone down.
				break;
			case 1:
				// After current line
				insertIntoPosition++;
				break;
			case 2:
				// Top of table
				insertIntoPosition = 0;
				break;
			case 3:
			default:
				// Bottom of table
				insertIntoPosition = [ruleList count];
		}
		
		[ruleList insertObject:lastObj atIndex:insertIntoPosition];
	}

	ruleListEnum = [ruleList objectEnumerator];
	while( obj = [ruleListEnum nextObject] )
	{
		[obj setObject:[NSNumber numberWithInt:counter + 1] forKey:@"order"];

		[ruleList replaceObjectAtIndex:counter withObject:obj];
		
		counter++;
	}
}

#pragma mark Hidden Character Handling
- (IBAction)showDTMnemonics:(id)sender
{
	[fileData toggleDTMnemonics:[sender state]];

	[self runRules:nil];
}

#pragma mark Extra Window handling
- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode  contextInfo:(void *)contextInfo
{
	if( (returnCode == NSOKButton) && ([(NSString *)contextInfo isEqualTo:@"Open Raw Agate File"]) )
	{
		NSString *extractionError;
		
		/* The open panel can't select multiple values.  We know what the user picked is at index 0 */
		NSLog( @"path selected: %@", [[sheet filenames] objectAtIndex:0] );
		[savedLastOpenPath setString:[[sheet filenames] objectAtIndex:0]];

		if( fileData == nil )
		{
			NSLog( @"loading the file" );
			fileData = [JTFileData initWithPath:[[sheet filenames] objectAtIndex:0]];
			[fileData retain];
		}
	
		// Was there an error extracting the story text from the file?
		extractionError = [fileData getExtractionError];
		if( extractionError != nil )
		{
			// yes, there was... crap.
			[errorText setStringValue:extractionError];

			[NSApp beginSheet:errorWindow
				modalForWindow:controlWindow
				modalDelegate:self
				didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
				contextInfo:@"failed to properly load raw agate file"];
			
			[fileData release];
			fileData = nil;
			
		}
		else
		{
			[self updateTextView:[fileData originalFileData]];
			
			// 01.12.06 - jturner - We found that in rare occasions, the incoming file isn't a 
			//                      UTF-8 friendly file.  So we do a last-ditch attempt in JTFileData
			//                      to open the file as ASCII.  But that destroys special characters.
			//                      We allow it only because it's better than nothing, but we need to
			//                      indicate to the user that special characters are going to be 
			//                      destroyed.
			//                      It's also worth noting that this is a specific problem to the tester
			//                      and not to WireSpeed.  [NSString initWithData:encoding:] seems to be
			//                      a bit over-zealous with its file-type checking.
			if( [fileData openedAsASCII] )
			{
				[asciiError setStringValue:@"WARNING: File opened in ASCII mode!"];
				[asciiError setToolTip:@"This means special characters (emdash, thinspace, etc) will not show up correctly.  Pay special attention to these characters in this translation."];
			}
		}
	}

	if( (returnCode == NSOKButton) && ([(NSString *)contextInfo isEqualTo:@"Load Base Translation"]) )
	{
		NSMutableArray *tmpRuleList = (NSMutableArray *)[NSArray arrayWithContentsOfFile:[[sheet filenames] objectAtIndex:0]];
	
		if( [[tmpRuleList objectAtIndex:0] objectForKey:@"theRule"] == nil )
		{
			[errorText setStringValue:@"This appears to either be an invalid base translation file or a pre v.2 copy.  "
									  @"Please make sure you are using the correct file or run the AgateConverter on this file first."];

			[NSApp beginSheet:errorWindow
				modalForWindow:controlWindow
				modalDelegate:self
				didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
				contextInfo:@"bad base translation file"];
		}
		else
		{
			// Convert the plist format back into an application-friendly format
			baseRuleList = [NSMutableArray new];
			NSEnumerator   *baseRuleListEnum;
			id obj;
		
			baseRuleListEnum = [tmpRuleList objectEnumerator];
			while( obj = [baseRuleListEnum nextObject] )
			{
				[baseRuleList addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
										[obj objectForKey:@"order"], @"order",
										[[JTRule alloc] initWithDictionary:[obj objectForKey:@"theRule"]], @"theRule",
										nil]];
			}

			[baseTranslationStatus setStringValue:[NSString stringWithFormat:@"Base loaded: %@ (%u rule%@)", 
					[[[sheet filenames] objectAtIndex:0] lastPathComponent], [baseRuleList count], ([baseRuleList count] == 1) ? @"" : @"s" ]];

			/* Make sure to retain the new guy */
			[baseRuleList retain];
		}
	}

	if( (returnCode == NSOKButton) && ([(NSString *)contextInfo isEqualTo:@"Load Saved Translation"]) )
	{
		NSMutableArray *tmpRuleList = (NSMutableArray *)[NSArray arrayWithContentsOfFile:[[sheet filenames] objectAtIndex:0]];

		// Is this a valid translation file?
		if( [[tmpRuleList objectAtIndex:0] objectForKey:@"theRule"] == nil )
		{
			[errorText setStringValue:@"This appears to either be an invalid translation file or a pre v.2 copy.  "
									  @"Please make sure you are using the correct file or run the AgateConverter on this file first."];
			
			[NSApp beginSheet:errorWindow
				modalForWindow:controlWindow
				modalDelegate:self
				didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
				contextInfo:@"bad translation file"];
		}
		else
		{
			/* The user would like to load in an unfinished or previously built translation table 
			   But we init a Mutable Array in init(), so let's whack that guy first.
			*/
			[ruleList release];

			[savedLastOpenPath setString:[[sheet filenames] objectAtIndex:0]];

			// Convert the plist format back into an application-friendly format
			ruleList = [NSMutableArray new];
			NSEnumerator   *ruleListEnum;
			id obj;
		
			ruleListEnum = [tmpRuleList objectEnumerator];
			while( obj = [ruleListEnum nextObject] )
			{
				[ruleList addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
										[obj objectForKey:@"order"], @"order",
										[[JTRule alloc] initWithDictionary:[obj objectForKey:@"theRule"]], @"theRule",
										nil]];
			}

			/* Make sure to retain the new guy */
			[ruleList retain];
			[ruleTable reloadData];
		}
	}
}

- (void)savePanelDidEnd:(NSSavePanel *)sheet returnCode:(int)returnCode  contextInfo:(void  *)contextInfo
{
	if( (returnCode == NSOKButton) && ([(NSString *)contextInfo isEqualTo:@"Save Translation"]) )
	{
		NSLog( @"Filename chosen: %@", [sheet filename] );
		[savedTranslationPath setString:[sheet filename]];
		
		// Convert the ruleList into something writeToFile: can deal with
		NSMutableArray *tmpRuleList  = [NSMutableArray new];
		NSEnumerator   *ruleListEnum;
		id obj;
	
		ruleListEnum = [ruleList objectEnumerator];
		while( obj = [ruleListEnum nextObject] )
		{
			// obj is our Dictionary.
			[tmpRuleList addObject:[NSDictionary dictionaryWithObjectsAndKeys:
								   [obj objectForKey:@"order"], @"order",
								   [[obj objectForKey:@"theRule"] returnObjectAsDictionary], @"theRule",
								   nil]];
		}

		if( [tmpRuleList writeToFile:[sheet filename] atomically:YES] == NO )
		{
			[errorText setStringValue:@"Unable to save file... make sure you are saving into a location for which you have appropriate write privileges."];

			[NSApp beginSheet:errorWindow
					modalForWindow:controlWindow
					modalDelegate:self
					didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
					contextInfo:@"error saving file"];
		}

		[tmpRuleList release];
	}

	if( (returnCode == NSOKButton) && ([(NSString *)contextInfo isEqualTo:@"Save As Base Translation"]) )
	{
		NSLog( @"Filename chosen: %@", [sheet filename] );
		
		[ruleList writeToFile:[sheet filename] atomically:YES];
	}

	if( (returnCode == NSOKButton) && ([(NSString *)contextInfo isEqualTo:@"Save Translated Text"]) )
	{
		NSLog( @"Filename chosen: %@", [sheet filename] );
		[savedTranslatedTextPath setString:[sheet filename]];
		
		NSString *tmpString;
		
		/* We run a mnemonic to unicode just to make sure if we're showing them, we don't push them
		   into the InDesign output.  A nice courtesy, I guess.  convertTranslationToInDesign: does
		   both the mnemonic and unicode to indesign conversions.
		*/
		tmpString = [perl convertTranslationToInDesign:[translationTextView string]];
		
		[tmpString writeToFile:[sheet filename] atomically:YES encoding:NSUTF8StringEncoding error:nil];
	}
		
}

- (void) sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
	if( (returnCode == 0) && (contextInfo == @"new rule") )
	{
		// Add the rule to the list.  Create a rule object for each rule that is created.
		// Autorelease that sucker so that when we leave this routine, the retain count
		// on the rule itself is at 1 instead of 2.
		JTRule *thisRule = [[JTRule new] autorelease];

		[thisRule setActive:1];
		[thisRule setFind:[NSString stringWithString:[perl unicodeToMnemonic:[findStringTextView string]]]];
		[thisRule setReplace:[NSString stringWithString:[perl unicodeToMnemonic:[replaceStringTextView string]]]];
		[thisRule setIsCaseSensitive:[check_caseSensitiveSearch state]];
		[thisRule setIsExhaustiveReplace:[check_exhaustiveReplace state]];
		[thisRule setIsReplaceFirstOnly:[check_replaceFirstOnly state]];
		[thisRule setIsReplaceLastOnly:[check_replaceLastOnly state]];
		[thisRule setIsRegex:[check_usePerlExpressions state]];
		[thisRule setTranslationInformation:[NSString stringWithString:[perl unicodeToMnemonic:[translationInformation string]]]];

		[ruleList addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
								@"", @"order",
								thisRule, @"theRule",
								nil]];

		/* We leave the order blank because we reset the order number based on the rule's
		   position in the array.  That way, we don't need to keep any counters around, 
	       which makes us happy!
		*/
		
		[self updateRuleOrders:[placeRuleWhereDropdown indexOfSelectedItem]];

		/* Update the rule table in the interface */
		[ruleTable reloadData];

		// Set the highlighted line to our new line then scroll to it
		// Depending on what the user picked for placement and where we're currently at, we 
		// can figure out what line to highlight
		int tableLocation = [ruleTable selectedRow];
		switch( [placeRuleWhereDropdown indexOfSelectedItem] )
		{
			case 0:
				// Before current line
				break;
			case 1:
				// After current line
				tableLocation++;
				break;
			case 2:
				// Top of table
				tableLocation = 0;
				break;
			case 3:
			default:
				// Bottom of table
				tableLocation = [ruleList count] - 1;
		}
				
		[ruleTable selectRowIndexes:[NSIndexSet indexSetWithIndex:(tableLocation)] byExtendingSelection:NO];
		[ruleTable scrollRowToVisible:[ruleTable selectedRow]];
	}

	if( (returnCode == 0) && (contextInfo == @"delete rule") )
	{
		if( [ruleTable selectedRow] >= 0 )
		{
			// Since the rule object was autoreleased at creation, there is no need to 
			// release it before removing it from the array.  removeObjectAtIndex: sends a
			// release message to all of its items before it destroys them.   (Which in this
			// case is an NSDictionary, which does the same process of sending its items release
			// messages before destruction.
			// Sorry for the lengthy OOP lesson, I sometimes forget how object deallocation is 
			// handled sometimes:)
		
			// Delete the dictionary entry from the array
			[ruleList removeObjectAtIndex:[ruleTable selectedRow]];

			// Update the rule orders
			[self updateRuleOrders:-1];
			
			// Update the rule table
			[ruleTable reloadData];
		}
	}

	if( (returnCode == 0) && (contextInfo == @"edit rule") )
	{
		if( [ruleTable selectedRow] >= 0 )
		{
			NSLog( @"editing line %d", [ruleTable selectedRow] );
		
			JTRule *tmpRule;

			tmpRule = [[ruleList objectAtIndex:[ruleTable selectedRow]] objectForKey:@"theRule"];
	
			// Updates the rule values
			[tmpRule setFind:[NSString stringWithString:[perl unicodeToMnemonic:[findStringTextView string]]]];
			[tmpRule setReplace:[NSString stringWithString:[perl unicodeToMnemonic:[replaceStringTextView string]]]];
			[tmpRule setIsCaseSensitive:[check_caseSensitiveSearch state]];
			[tmpRule setIsExhaustiveReplace:[check_exhaustiveReplace state]];
			[tmpRule setIsReplaceFirstOnly:[check_replaceFirstOnly state]];
			[tmpRule setIsReplaceLastOnly:[check_replaceLastOnly state]];
			[tmpRule setIsRegex:[check_usePerlExpressions state]];
			[tmpRule setTranslationInformation:[NSString stringWithString:[perl unicodeToMnemonic:[translationInformation string]]]];

			// Update the rule table
			[ruleTable reloadData];
		}
	}

	if( (returnCode == 0) && (contextInfo == @"quit app") )
	{
		/* user really wanted to quit.  Unbind the delegate so that we don't get into a 
		   "are you sure you wanna quit?" loop 
		*/
		[[NSApplication sharedApplication] setDelegate:nil];
		[[NSApplication sharedApplication] terminate:nil];
	}
}

- (void) updateTextView:(NSString *)newText
{
	if( newText != nil )
	{
		[translationTextView setString:newText];
	}
}

#pragma mark NSApplication Delegates
- (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
{
	/* We display the error window asking if they want to quit, but tell
	   NSApp to *not* quit here.  If they choose to quit, we'll send NSApp a quit
	   message under sheetDidEnd:
	*/
	
	[errorText setStringValue:@"Are you sure you want to quit?\nHave you saved your translation file recently?"];

	[NSApp beginSheet:errorWindow
			modalForWindow:controlWindow
			modalDelegate:self
			didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
			contextInfo:@"quit app"];

	return( NSTerminateCancel );
}

#pragma mark NSTableView items
- (IBAction) tableDoubleClicked:(id)sender
{
	[self editRule:sender];
}

#pragma mark Other Interface Items

#pragma mark Debugging routines
- (IBAction) dumpRuleList:(id)sender
{
	NSLog( @"ruleList: %@", ruleList );
	NSLog( @"baseRuleList: %@", baseRuleList );
}

@end

/*-------------------------------------------------------------------------*/

@implementation Controller (NSTableDataSource)

- (int) numberOfRowsInTableView:(NSTableView *) aTableView
{
	// Return the number of entries in the storage area
	return [ruleList count];
}

- (id) tableView:(NSTableView *) aTableView objectValueForTableColumn:(NSTableColumn *) aTableColumn row:(int)rowIndex
{
	// Are we looking for the order?
	if( [[aTableColumn identifier] isEqualTo:@"order"] )
	{
		return [(NSDictionary *) [ruleList objectAtIndex:rowIndex] objectForKey:[aTableColumn identifier]];
	}
	else
	{
		// First, get the rule object
		JTRule *theRule = [(NSDictionary *) [ruleList objectAtIndex:rowIndex] objectForKey:@"theRule"];
	
		// Then ask the rule to return a formatted dictionary of its contents, then grab the column information
		return [[theRule returnObjectAsDictionary] objectForKey:[aTableColumn identifier]];
	}
}

@end
